#pragma once
#include <cerrno>
#include <cstring>
#include <iostream>
#include <functional>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "error.hpp"

namespace ns_server
{
    const uint16_t default_port = 8888;
    const std::string default_ip = "192.168.20.8";
    using func = std::function<std::string(std::string)>;
    class UdpServer
    {
    public:
        // 云服务器和端口号不能够自己设定ip
        UdpServer(func cb,uint16_t port = default_port /*,std::string ip = default_ip*/) :
        _service(cb),
        _port(port) /*,_ip(ip)*/
        {

        }
        void InitServer()
        {
            // 创建套接字
            _socket = socket(AF_INET, SOCK_DGRAM, 0);
            // AF_INET：创建网络通信的套接字
            // SOCK_DGRAM：面向字节流
            if (_socket < 0)
            {
                // strerror：打印错误码对应的信息
                std::cerr << "create socket failed... " << strerror(errno) << std::endl;
                exit(SOCKET_ERR);
            }

            // 绑定端口号和ip
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local)); // 对local进行清0
            // 未来这些数据都是要在网络中传输的，因此都要转化为网络序列
            local.sin_port = htons(_port);
            local.sin_family = AF_INET; // 表明是网络通信还是本主机去通信

            // local.sin_addr.s_addr = inet_addr(_ip.c_str());  //inet_addr：将点分十进制的ip转化为4字节整数，并将它转化为网络序列
            // Cannot assign requested address 这样子去设ip会直接报这个错误，不能你自己去设置（虚拟机和云服务器上都会这样出错）
            // 得用真实的绑ip的方法去绑定

            local.sin_addr.s_addr = INADDR_ANY; // 表明让服务器，在启动的时候可以bind本主机上任意ip

            if (bind(_socket, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                std::cerr << "bind socket failed... " << strerror(errno) << std::endl;
                exit(BIND_ERR);
            }
            std::cerr << "bind socket success... " << std::endl;

            // udp初始化工作，只要进行创建socket，并绑定端口号，ip地址即可。
        }
        void Start()
        {
            std::cout << "udp server start..." << std::endl;
            // 开始通信，抖音在晚上你也是可以访问的，正确的是任何时刻都可以被访问
            // 所以它肯定是一个死循环

            while (true)
            {
                // 从客户端中获取数据
                char buf[1024] = {0};
                struct sockaddr_in peer;      // peer 远端的意思
                socklen_t len = sizeof(peer); // 在调用recvfrom时，要先填充len，recvfrom执行完后，len才会被重置为实际的大小
                int n = recvfrom(_socket, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&peer, &len);
                if (n < 0)
                    continue;

                buf[n] = 0;
                uint16_t clientport = htons(peer.sin_port);
                std::string clientip = inet_ntoa(peer.sin_addr);
                //debug
                std::cout << "client " << clientip << "-" << clientport << "#" << buf << std::endl;
                // 将获取的数据进行业务处理

                std::string respond = _service(buf);  


                // 再将数据发送给客户端
                sendto(_socket, respond.c_str(), respond.size(), 0, (struct sockaddr *)&peer, len);
            }
        }
        ~UdpServer()
        {
        }

    private:
        func _service; //由上层（即.cc传过来的业务处理），上层处理完后，再将处理后的数据发送给客户端
        uint16_t _port;
        // std::string _ip;   // 云服务器和端口号不能够自己设定ip
        int _socket; // 套接字文件描述符
    };
}